package fem2.jlinalg;

import fem2.Debugger;
import fem2.NotImplementedError;
import fem2.enu.EchoLevelType;
import fem2.enu.PETScKSPType;
import fem2.enu.PETScPrecondType;
import fem2.jnative.JPETSc;
import inf.jlinalg.IMatrix;
import inf.jlinalg.IMatrixReadOnly;
import inf.jlinalg.SolveFailedException;

public class PETScLSESolver extends Solver {

	static {
		try {
			// System.load("/opt/openmpi/lib/libmpi.so.1");
			// System.load("/opt/openmpi/lib/libmpi.so");
			System.load("/opt/openmpi/lib/libmpi_f77.so.1");
			System.load("/opt/petsc/petsc-dev/lib/libpetsc.so");
			System.load("/home/hbui/workspace2/jni/JPETSc/Release/libJPETSc.so");
		} catch (UnsatisfiedLinkError e) {
			System.err
					.println("Native code library failed to load. See the chapter on Dynamic Linking Problems in the SWIG Java documentation for help.\n"
							+ e);
			System.exit(1);
		}
	}

	/**
	 * a class to handle matrix interfacing with PETSc
	 * 
	 * @author hbui
	 * 
	 */
	class PETScMatrix implements IMatrix {

		private static final long serialVersionUID = 1L;

		private JPETSc jp;

		public PETScMatrix(JPETSc jp) {
			this.jp = jp;
		}

		@Override
		public double get(int i, int j) {
			return jp.lhsGet(i, j);
		}

		@Override
		public int getColumnCount() {
			return jp.getSize();
		}

		@Override
		public int getRowCount() {
			return jp.getSize();
		}

		@Override
		public void add(int i, int j, double value) {
			jp.lhsAdd(i, j, value);
		}

		@Override
		public void set(int i, int j, double value) {
			jp.lhsSet(i, j, value);
		}

		@Override
		public void assign(IMatrixReadOnly a) {
			/*
			 * TODO
			 */
			throw new NotImplementedError();
		}

		@Override
		public Object clone() {
			/*
			 * TODO
			 */
			throw new NotImplementedError();
		}
	}

	PETScMatrix A;
	private JPETSc jp;

	/**
	 * constructor with argument
	 * 
	 * @param argv
	 *            argument to pass to PETSc solver
	 */
	public PETScLSESolver(String[] argv) {
		jp = new JPETSc(argv);
		A = new PETScMatrix(jp);
	}

	public PETScLSESolver() {
		jp = new JPETSc();
		A = new PETScMatrix(jp);
	}

	@Override
	public IMatrix getA() {
		return A;
	}

	@Override
	public void setSize(int dimk) {
		jp.setSize(dimk);
	}

	/**
	 * setSize with preallocation for PETSc
	 * 
	 * @param dimk
	 * @param nz
	 * @param nnz
	 */
	public void setSize(int dimk, int nz, int[] nnz) {
		jp.setSize(dimk, nz, nnz);
	}

	@Override
	public void setSize(SymbolicTable t) {
		throw new Error("this method is not necessary in PETSc");
	}

	/**
	 * set preconditioner for PETSc
	 * 
	 * @param type
	 */
	public void setPC(PETScPrecondType type) {
		jp.setPC(type.getName());
	}

	/**
	 * set Krylov subspace method for PETSc
	 * 
	 * @param type
	 */
	public void setKSP(PETScKSPType type) {
		jp.setKSP(type.getName());
	}

	@Override
	public void initialize() {
		jp.initialize();
	}

	@Override
	public void solve(double[] b) throws SolveFailedException {
		long start = System.currentTimeMillis();
		for (int i = 0; i < b.length; i++) {
			jp.rhsSet(i, b[i]);
		}
		jp.solve(b);
		long end = System.currentTimeMillis();
		if (getEchoLevel().contain(EchoLevelType.AUX1)) {
			System.out.println("Solving time: " + (end - start) + " ms.");
		}
	}

	@Override
	public void finalize() {
		jp.delete();
		if (getEchoLevel().contain(EchoLevelType.OUTPUT)) {
			Debugger.watch(getClass().getName() + " finalized");
		}
	}

}
